//
//  test.swift
//  SYGovernment
//
//  Created by 思域 on 2020/9/27.
//  Copyright © 2020 思域. All rights reserved.
//

import Foundation
import UIKit
import CommonCrypto

/// 加密工具
public struct EncryptUtil {
    /// des加密
    public static func desEncrypt(_ string: String, key: String) -> String? {
        guard let base64 = string.data(using: .utf8)?.base64EncodedString() else {
            return nil
        }
        return base64.desEncrypt(key: key)
    }

    /// des解密
    public static func desDecrypt(_ string: String, key: String) -> String? {
        let decoded = string.desDecrypt(key: key)
        return decoded
    }

}

fileprivate extension String {

    func desEncrypt(key: String, options: Int = kCCOptionPKCS7Padding) -> String? {
        if let keyData = key.data(using: String.Encoding.utf8),
           let data = self.data(using: String.Encoding.utf8),
           let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeDES) {


            let keyLength = size_t(kCCKeySizeDES)
            let operation: CCOperation = UInt32(kCCEncrypt)
            let algoritm: CCAlgorithm = UInt32(kCCAlgorithmDES)
            let options: CCOptions = UInt32(options + kCCOptionECBMode)


            var numBytesEncrypted: size_t = 0

            let cryptStatus = CCCrypt(operation,
                    algoritm,
                    options,
                    (keyData as NSData).bytes, keyLength,
                    nil,
                    (data as NSData).bytes, data.count,
                    cryptData.mutableBytes, cryptData.length,
                    &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                let d = cryptData as Data
                var str = d.map {
                    String(format: "%02x", $0)
                }.joined(separator: "")
                while str.hasSuffix("00") {
                    str.removeLast(2)
                }
                return str
            } else {
                return nil
            }
        }
        return nil
    }

    private func hexadecimal() -> Data? {
        var data = Foundation.Data(capacity: count / 2)

        let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
        regex.enumerateMatches(in: self, range: NSMakeRange(0, utf16.count)) { match, flags, stop in
            let byteString = (self as NSString).substring(with: match!.range)
            var num = UInt8(byteString, radix: 16)!
            data.append(&num, count: 1)
        }

        guard data.count > 0 else {
            return nil
        }

        return data
    }

    func desDecrypt(key: String, options: Int = kCCOptionPKCS7Padding) -> String? {
        if let keyData = key.data(using: String.Encoding.utf8) {
            let data = hexadecimal()! as NSData
            let cryptData = NSMutableData(length: Int((data.length)) + kCCBlockSizeDES)!

            let keyLength = size_t(kCCKeySizeDES)
            let operation: CCOperation = UInt32(kCCDecrypt)
            let algoritm: CCAlgorithm = UInt32(kCCAlgorithmDES)
            let options: CCOptions = UInt32(options + kCCOptionECBMode)

            var numBytesEncrypted: size_t = 0

            let cryptStatus = CCCrypt(operation,
                    algoritm,
                    options,
                    (keyData as NSData).bytes, keyLength,
                    nil,
                    data.bytes, data.length,
                    cryptData.mutableBytes, cryptData.length,
                    &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                let unencryptedMessage = String(data: cryptData as Data, encoding: .utf8)
                return unencryptedMessage?.base64Decoded()
            } else {
                return nil
            }
        }
        return nil
    }

}

public enum CryptoAlgorithm {
    case AES, AES128, DES, DES3, CAST, RC2, RC4, Blowfish
    var algorithm: CCAlgorithm {
        var result: UInt32 = 0
        switch self {
        case .AES:
            result = UInt32(kCCAlgorithmAES)
        case .AES128:
            result = UInt32(kCCAlgorithmAES128)
        case .DES:
            result = UInt32(kCCAlgorithmDES)
        case .DES3:
            result = UInt32(kCCAlgorithm3DES)
        case .CAST:
            result = UInt32(kCCAlgorithmCAST)
        case .RC2:
            result = UInt32(kCCAlgorithmRC2)
        case .RC4:
            result = UInt32(kCCAlgorithmRC4)
        case .Blowfish:
            result = UInt32(kCCAlgorithmBlowfish)
        }
        return CCAlgorithm(result)
    }

    var keyLength: Int {
        var result: Int = 0
        switch self {
        case .AES:
            result = kCCKeySizeAES128
        case .AES128:
            result = kCCKeySizeAES256
        case .DES:
            result = kCCKeySizeDES
        case .DES3:
            result = kCCKeySize3DES
        case .CAST:
            result = kCCKeySizeMaxCAST
        case .RC2:
            result = kCCKeySizeMaxRC2
        case .RC4:
            result = kCCKeySizeMaxRC4
        case .Blowfish:
            result = kCCKeySizeMaxBlowfish
        }
        return Int(result)
    }

    var cryptLength: Int {
        var result: Int = 0
        switch self {
        case .AES:
            result = kCCKeySizeAES128
        case .AES128:
            result = kCCBlockSizeAES128
        case .DES:
            result = kCCBlockSizeDES
        case .DES3:
            result = kCCBlockSize3DES
        case .CAST:
            result = kCCBlockSizeCAST
        case .RC2:
            result = kCCBlockSizeRC2
        case .RC4:
            result = kCCBlockSizeRC2
        case .Blowfish:
            result = kCCBlockSizeBlowfish
        }
        return Int(result)
    }

}

public extension NSData {
    /**
     加密
     - parameter algorithm:加密方式
     - parameter keyData:加密key
     - return NSData :加密后的数据
     */
    func enCrypt(algorithm: CryptoAlgorithm, keyData: NSData) -> NSData {
        return crypt(algorithm: algorithm, operation: CCOperation(kCCEncrypt), keyData: keyData)!
    }

    /// 解密
    /// - Parameters:
    ///   - algorithm: 解密方式
    ///   - keyData: 解密key
    func deCrypt(algorithm: CryptoAlgorithm, keyData: NSData) -> NSData? {
        return crypt(algorithm: algorithm, operation: CCOperation(kCCDecrypt), keyData: keyData)
    }


    private func crypt(algorithm: CryptoAlgorithm, operation: CCOperation, keyData: NSData) -> NSData? {
        let keyBytes = keyData.bytes
        let keyLength = Int(algorithm.keyLength)
        let datalength = self.length
        let dataBytes = self.bytes
        let cryptLength = Int(datalength + algorithm.cryptLength)
        let cryptPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: cryptLength)
        let algoritm: CCAlgorithm = CCAlgorithm(algorithm.algorithm)
        let option: CCOptions = CCOptions(kCCOptionECBMode + kCCOptionPKCS7Padding)
        let numBytesEncrypted = UnsafeMutablePointer<Int>.allocate(capacity: 1)
        numBytesEncrypted.initialize(to: 0)
        let cryptStatus = CCCrypt(operation, algoritm, option, keyBytes, keyLength, nil, dataBytes, datalength, cryptPointer, cryptLength, numBytesEncrypted)

        // 判断是否加密成功
        if CCStatus(cryptStatus) == CCStatus(kCCSuccess) {
            let len = Int(numBytesEncrypted.pointee)
            let data: NSData = NSData(bytesNoCopy: cryptPointer, length: len)
            numBytesEncrypted.deallocate()
            return data
        } else {
            numBytesEncrypted.deallocate()
            cryptPointer.deallocate()
            return nil
        }
    }
}
